home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World Komputer 2010 April
/
PCWorld0410.iso
/
pluginy Firefox
/
2410
/
2410.xpi
/
chrome
/
content
/
foxmarks-server.js
< prev
next >
Wrap
Text File
|
2010-01-28
|
61KB
|
1,802 lines
/*
Copyright 2007-2008 Foxmarks Inc.
foxmarks-server.js: component that implements the logical interface to
the server.
*/
// TO DO:
// * Deal with failure to retrieve baseline state from server -> merge
function getDatasourceAttribute(synctype, attr){
switch(synctype){
case 'bookmarks':
switch(attr){
case 'engine':
return BookmarkDatasource.STORAGE_ENGINE;
default:
throw("getDatasourceAttribute: unknown attribute (" + attr + ")");
}
break;
case 'lastmodifieddate':
break;
default:
throw("getDatasourceAttribute: unknown synctype (" + synctype + ")");
}
}
function hasPasswordSync(){
return "@mozilla.org/login-manager;1" in Cc;
}
var SYNCSET = {};
if(hasPasswordSync()){
SYNCSET = {
"bookmarks": BookmarkDatasource,
"passwords": PasswordDatasource
};
}
else {
SYNCSET = {
"bookmarks": BookmarkDatasource
};
}
function createDatasource(type){
var model = new SYNCSET[type];
if(model === undefined)
throw("createDatasource: unknown synctype (" + type +")");
return model;
}
function loadDatasourceSet(allItems) {
var result = [];
var type;
var obj = {};
for(type in SYNCSET){
if(obj[type] === undefined){
if(Xmarks.gSettings.isSyncEnabled(type) || allItems){
var model = createDatasource(type);
result.push(model);
}
}
}
return result;
}
function SyncServer() {
}
SyncServer.prototype = {
_syncEngine: null,
_request: null,
_baselineCache: {},
cancel: function() {
if(this._syncEngine) {
this._syncEngine.cancel();
}
if (this._request){
this._request.Cancel();
}
},
datacancel: function(){
if (this._request){
this._request.Cancel();
}
},
_createSyncEngine: function(datasource){
var cls = Xmarks.gSettings.useOwnServer ?
OwnSyncEngine :
Syncd2SyncEngine;
var result = new cls(this,datasource);
result.manual = this.manual;
return result;
},
getBaseline: function(syncType,callback){
var ds = createDatasource(syncType);
var se = this._createSyncEngine(ds);
se._fetchBaseline(function(status){
if(se._responseOK(status, callback)){
callback(0, se._baseline);
}
});
},
suspendWatcher: function(state){
var os = Cc["@mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
os.notifyObservers(null, "foxmarks-watchersuspend",
state ? "1" : "0");
},
_doPerDatasource: function(funcname, callback, arg2){
var list = loadDatasourceSet();
var that = this;
var ds = {};
try {
that.suspendWatcher(true);
// loop through each set of syncdata, using the
// chained callback mechanism, dropping out if
// we encounter an error
var mycallback = function(statuscode){
if(statuscode == 0 || statuscode == 444){
// 444 -handle purged state
if(statuscode == 444){
// if we allow purge from other data types
// we'll need to modify this string
Xmarks.Alert(Xmarks.Bundle().GetStringFromName(
"msg.passwordsyncpurged"));
Xmarks.gSettings.setSyncEnabled(ds.syncType, false);
statuscode = 0;
}
if(ds && ds.syncType !== undefined){
Xmarks.gSettings.SyncComplete(ds.syncType);
Xmarks.SetProgressComponentStatus(ds.syncType, "end");
}
ds = list.shift();
if(ds){
that._syncEngine = that._createSyncEngine(ds);
Xmarks.SetProgressComponentStatus(ds.syncType, "start");
if(arg2 !== undefined){
that._syncEngine[funcname](arg2, mycallback);
}
else {
that._syncEngine[funcname](mycallback);
}
}
else {
callback(statuscode);
that.suspendWatcher(false);
that._syncEngine = null;
if(!Xmarks.gSettings.useBaselineCache){
that._baselineCache = {};
}
}
}
else {
callback(statuscode);
that.suspendWatcher(false);
that._syncEngine = null;
if(!Xmarks.gSettings.useBaselineCache){
that._baselineCache = {};
}
}
}
mycallback(0);
}catch(e){
that.suspendWatcher(false);
if(typeof e == "object" && e.message){
Xmarks.LogWrite("Sync Error: " + e.message + "(" +
e.fileName + ": " + e.lineNumber + ")");
} else {
Xmarks.LogWrite("Synchronization failed. Error is " +
e.toSource() + " (type = " + typeof e + ")");
}
if(typeof e == "number"){
mycallback(e);
} else {
mycallback(4);
}
}
},
sync: function(prevState, callback){
Xmarks.SetProgressMessage("progress.syncing");
this._doPerDatasource("sync", callback, prevState);
},
upload: function(callback){
Xmarks.SetProgressMessage("progress.syncing");
this._doPerDatasource("upload", callback);
},
download: function(callback){
Xmarks.SetProgressMessage("progress.syncing");
this._doPerDatasource("download", callback);
},
merge: function(local, callback){
Xmarks.SetProgressMessage("progress.syncing");
this._doPerDatasource("merge", callback, local);
},
status: function(syncType, callback){
var ds = createDatasource(syncType);
var se = this._createSyncEngine(ds);
se.status(callback);
},
extstatus: function(syncType, callback){
var ds = createDatasource(syncType);
var se = this._createSyncEngine(ds);
se.extstatus(callback);
},
getProfileNames: function(callback){
var se = this._createSyncEngine(null);
se.getProfileNames(callback);
},
_getCorrelatorInfo: function(url, corruri, callback, flags){
const https = "https://";
flags = flags || {};
var str;
var self = this;
var protocol = url.substr(0,https.length).toLowerCase();
var funcFinished = function(status, response) {
self._request = null;
callback(status, response);
};
// we contact correlator via https if the url we are interested
// in is https
if (Xmarks.gSettings.securityLevel == 1 || protocol == https ){
str = https;
} else {
str = "http://";
}
str += Xmarks.gSettings.apiHost + corruri;
this._request = new Request(
"POST",
str,
{
urls: [url],
mid: Xmarks.gSettings.machineId,
apihost: Xmarks.gSettings.apiHost,
drifthost: Xmarks.gSettings.driftHost,
statichost: Xmarks.gSettings.staticHost,
cid: "xmfx"
},
{
isAuthRequest: false,
headers: null,
ignoreBody: false,
noJSON: flags.noJSON,
noAuthDialog: true
}
);
this._request.Start(funcFinished);
},
getSimilarSites: function(url, callback){
return this._getCorrelatorInfo(
url,
"/internal/bib",
callback,
{
noJSON: true
}
);
},
getTurboTags: function(url, callback){
return this._getCorrelatorInfo(url, "/internal/topics_review/read",
callback);
},
updateReview: function(url, url_id, rating, review, callback) {
var useSSL = (Xmarks.gSettings.securityLevel == 1 ||
url.indexOf("https://") == 0);
this._request = new Request("POST",
{ protocol: useSSL ? "https" : "http",
host: Xmarks.gSettings.apiHost,
path: "/internal/review/write" },
{ url_id: url_id, rating: rating, review: review });
this._request.Start(callback);
},
purgepasswords: function(callback){
var ds = createDatasource("passwords");
var se = this._createSyncEngine(ds);
se.purgepasswords(callback);
},
restore: function(rev,callback){
var ds = createDatasource("bookmarks");
var se = this._createSyncEngine(ds);
try {
se.restore(rev,callback);
} catch(e){
Xmarks.LogWrite("Restore Failed (" + e.message + ")");
if(typeof e == "number"){
callback(e);
} else {
callback(4);
}
}
},
getrevision: function(rev,callback){
var ds = createDatasource("bookmarks");
var se = this._createSyncEngine(ds);
try {
se.getrevision(rev,callback);
} catch(e){
Xmarks.LogWrite("GetRevision Failed (" + e.message + ")");
if(typeof e == "number"){
callback(e);
} else {
callback(4);
}
}
},
getrevisions: function(callback){
var ds = createDatasource("bookmarks");
var se = this._createSyncEngine(ds);
try {
se.getrevisions(callback);
} catch(e){
Xmarks.LogWrite("GetRevision Failed (" + e.message + ")");
if(typeof e == "number"){
callback(e);
} else {
callback(4);
}
}
},
verifypin: function(pin, callback){
var ds = createDatasource("passwords");
var se = this._createSyncEngine(ds);
try {
se.verifypin(pin, callback);
} catch(e){
Xmarks.LogWrite("Verify Pin Failed (" + e.message + ")");
if(typeof e == "number"){
callback(e);
} else {
callback(4);
}
}
},
runUnitTest: function(){
gFoxmarksUT.run();
},
countItems: function(synctype, itemtype, callback){
var ds = createDatasource(synctype);
var se = this._createSyncEngine(ds);
se.countItems(itemtype, callback);
}
};
function SyncEngine(datasource) {
this._datasource = datasource;
}
SyncEngine.prototype = {
request: null,
_iscancelled: false,
get _baseline() {
return this._mgr._baselineCache[
this._datasource.syncType
];
},
set _baseline(val) {
this._mgr._baselineCache[
this._datasource.syncType
] = val;
},
cancel: function() {
if (this.request) {
this.request.Cancel();
}
this._iscancelled = true;
},
isCancelled: function(){
return this._iscancelled;
},
_responseOK: function(response, callback) {
if(this.isCancelled()){
callback(2);
return false;
}
else if (typeof response == 'number') {
if (!response) {
return true;
} else {
callback(response);
}
} else if (typeof response == 'object') {
if (!response.status) {
return true;
} else {
if (response.status == 403) {
Xmarks.LogWrite("Got a 403");
Handle403(response);
} else if (response.status == 503) {
try {
gServerBackoff[this._datasource.syncType] = response.backoff_delay || 0;
if (gServerBackoff[this._datasource.syncType]) {
Xmarks.LogWrite("Server wants us to wait " +
gServerBackoff[this._datasource.syncType] + " seconds");
}
} catch(e) {}
}
callback(response.status);
return false;
}
} else {
throw Error("unexpected response type: " + response);
}
},
countItems: function(itemtype, callback){
var self = this;
var ns = new Nodeset(self._datasource);
ns.FetchFromNative(function(status){
if (self._responseOK(status, callback)) {
var ctr = 0;
ns.OnTree(
function(nid){
if(ns.Node(nid).ntype == itemtype)
ctr++;
},
function(){
callback(status, ctr);
}
);
}
});
},
sync: function(prevState, callback) {
// Local Vars
var lns = null, sns = null;
var lcs = null, scs = null;
var sco = null;
var lastModified = 0;
var self = this;
var fms = Cc["@foxcloud.com/extensions/foxmarks;1"].
getService(Ci.nsIFoxmarksService);
self.startTime = Xmarks.LogTimer("Timer Sync Start");
var funcCheckStatus = function(status, response) {
if (self._responseOK(status, callback)) {
if (response.isreset == true) {
self.upload(callback);
} else {
self.merge(true, callback);
}
}
};
// Local functions
var funcGetServerChanges = function(status) {
if (self._responseOK(status, callback)) {
Xmarks.LogTimer("Timer Sync: Fetch Baseline", self.startTime);
// Get changes.
self._getServerChanges(funcGotServerChanges);
}
};
var funcGotServerChanges = function(status, serverContextObject) {
if (self._responseOK(status, callback)) {
Xmarks.LogTimer("Timer Sync: Got Server Changes", self.startTime);
sco = serverContextObject;
if (!sco.continuous) {
// Someone has done an upload. Allow the user the option
// of performing a merge or a download.
switch (self._datasource.DiscontinuityPrompt()) {
case 0: // merge
return self.merge(false, callback);
case 1: // download
return self.download(callback);
case 2: // cancel
callback(2);
return;
}
}
if (!sco.mscs.length && prevState == 'ready' &&
self._haveFetched) {
Xmarks.LogWrite("Nothing to see here; move along.");
callback(0);
return;
}
//Xmarks.SetProgressMessage("progress.syncing");
scs = sco.mscs;
sns = sco.sns;
// Calculate minimal local change set.
lastModified = fms.getLastModified(self._datasource.synctype);
lns = new Nodeset(self._datasource);
lns.FetchFromNative(funcGotLocalNodeset);
self._haveFetched = true;
}
}
var funcGotLocalNodeset = function(status) {
if (self._responseOK(status, callback)) {
Xmarks.LogTimer("Timer Sync: Fetched Local", self.startTime);
var base = new Nodeset(self._datasource,self._baseline);
base.Compare(lns, funcGetLocalCommandset);
}
}
var funcGetLocalCommandset = function(status, lcs) {
if (self._responseOK(status, callback)) {
Xmarks.LogTimer("Timer Sync: Command Set", self.startTime);
// Check to see whether there's been a clobber
if (lns.length < 3 * self._baseline.length / 4) {
Xmarks.LogWrite("Yikes! Length was " + self._baseline.length +
" but is now " + lns.length);
self.manual = true;
if (!self._datasource.ClobberDialog(lns.length, self._baseline.length)) {
// User canceled. Back off for 24 hours.
gBackoffUntil = Date.now() + 24 * 60 * 60 * 1000;
callback(2);
return;
}
}
Xmarks.LogWrite("lcs = " + lcs.length + " scs = " + scs.length);
try {
Synchronize(self._baseline, lcs, scs, lns, sns,
funcSyncComplete);
} catch (e) {
Xmarks.LogWrite("Synchronization failed. Error is " +
e.toSource());
if(typeof e == "number"){
callback(e);
} else {
callback(4);
}
}
}
};
var funcSyncComplete = function(finalLcs,
finalScs,
conflicts,
showedUI) {
Xmarks.LogTimer("Timer Sync: Sync Set", self.startTime);
//Xmarks.SetProgressMessage("progress.writing");
if (showedUI) {
self.manual = true;
}
if (lastModified != fms.getLastModified(self._datasource.synctype)) {
Xmarks.LogWrite("Local datastore changed during sync");
Xmarks.LogWrite("lastModified " + lastModified + " != " + fms.getLastModified(self._datasource.synctype));
callback(409);
return;
}
// Scoping: assign back to our enclosing function's locals
lcs = finalLcs;
scs = finalScs;
// Apply the server's changes to the local set.
try {
lns = new Nodeset(self._datasource, lns);
scs.execute(lns);
Xmarks.LogTimer("Timer Sync: Applied Changes", self.startTime);
} catch (e) {
Xmarks.LogWrite("execute failed: " + e.toSource());
if(typeof e == "number"){
callback(e);
} else {
callback(4);
}
return;
}
// Write local changes (if any) to the server.
if (lcs.set.length > 0) {
self._writeServerChanges(lcs, lns, sco, conflicts,
funcWroteServerChanges);
} else {
funcWroteServerChanges();
}
};
var funcWroteServerChanges = function(obj) {
if (obj) { // We had something to write
if (self._responseOK(obj, callback)) {
} else {
Xmarks.LogWrite("putchanges failed; response is " +
obj.toJSONString());
return;
}
}
Xmarks.LogTimer("Timer Sync: Sent Server Changes", self.startTime);
// Flush the local nodeset back to the native datastore if it changed.
if (scs.set.length > 0) {
//Xmarks.SetProgressMessage("progress.loading");
lns.FlushToNative(funcWroteLocalChanges);
} else {
funcWroteLocalChanges(0);
}
};
var funcWroteLocalChanges = function(response) {
// If we got changes from the server or wrote changes to
// the server, we'll have a new revision number. If neither
// of these happened, then the sync was a big no-op.
if (self._responseOK(response, callback)) {
Xmarks.LogTimer("Timer Sync: Wrote to Native", self.startTime);
if (lcs.set.length > 0 || scs.set.length > 0) {
self._completeTransaction(lns, sco, callback);
} else {
// We didn't do anything, but note that we successfully
// Synced nonetheless.
callback(0); // Done!
}
}
};
// function start
// If we've never synced, do an upload or merge.
if (!Xmarks.gSettings.getHaveSynced(self._datasource.syncType)) {
self.status(funcCheckStatus);
}
else {
if(Xmarks.gSettings.mustUpload(self._datasource.syncType)){
Xmarks.LogWrite("Forced Upload: Pin Reset");
self.upload(function(response){
if(self._responseOK(response, callback)){
Xmarks.gSettings.setMustUpload(self._datasource.syncType, false);
callback(response);
}
});
return;
}
else if(Xmarks.gSettings.mustMerge(self._datasource.syncType)){
Xmarks.LogWrite("Forced Merge for Passwords");
self.merge(true, function(response){
if(self._responseOK(response, callback)){
Xmarks.gSettings.setMustMerge(self._datasource.syncType, false);
callback(response);
}
});
return;
}
// Normal sync: make sure we've got a baseline to work with.
//Xmarks.SetProgressMessage("progress.downloading");
self._fetchBaseline(funcGetServerChanges);
return;
}
}
};
function Syncd2SyncEngine(mgr, datasource) {
this._datasource = datasource;
this._mgr = mgr;
}
Syncd2SyncEngine.prototype = new SyncEngine;
function OwnSyncEngine(mgr, datasource) {
this._datasource = datasource;
this._mgr = mgr;
}
OwnSyncEngine.prototype = new SyncEngine;
Syncd2SyncEngine.prototype._args = function(dict, ignoreUpload) {
if (this.manual) {
dict["manual"] = true;
}
if (gFailureCount) {
dict["retry"] = gFailureCount;
}
if (Xmarks.gSettings.machineId) {
if (!dict["log"]) {
dict["log"] = {};
}
dict["log"]["mid"] = Xmarks.gSettings.machineId;
dict["log"]["serp"] = Xmarks.gSettings.serpEnabled ? Xmarks.gSettings.serpMaxItems : 0;
dict["log"]["ssEnabled"] = Xmarks.gSettings.simsiteEnabled;
var st = [];
for(var x = 0; x < 10; x++){
st.push(Xmarks.gSettings.getST(false,x));
}
if(st.toSource() != "[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}]"){
dict["log"]["st"] = st;
}
st = [];
for(var x = 0; x < 10; x++){
st.push(Xmarks.gSettings.getST(true,x));
Xmarks.gSettings.clearST(x);
}
if(st.toSource() != "[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}]"){
dict["log"]["ust"] = st;
}
if(Xmarks.gSettings.trSERP){
dict["log"]["trs"] = Xmarks.gSettings.trSERP;
Xmarks.gSettings.trSERP = "";
}
if(Xmarks.gSettings.numTurboTags){
dict["log"]["ttags"] = Xmarks.gSettings.numTurboTags;
Xmarks.gSettings.numTurboTags = 0;;
}
}
if (Xmarks.gSettings.viewId) {
if(!ignoreUpload){
dict.view = Xmarks.gSettings.viewId;
}
}
return dict;
}
Syncd2SyncEngine.prototype.upload = function(callback) {
var self = this;
var ns = new Nodeset(this._datasource);
// if we are doing a force upload, then that trumps forcedMerge
if(Xmarks.gSettings.mustUpload(self._datasource.syncType)){
Xmarks.gSettings.setMustMerge(self._datasource.syncType, false);
}
// even though the user says upload, we don't want
// to do that for passwords during initial sync
if(Xmarks.gSettings.mustMerge(self._datasource.syncType)){
Xmarks.LogWrite("Forced Merge for Passwords");
self.merge(true, function(response){
if(self._responseOK(response, callback)){
Xmarks.gSettings.setMustMerge(self._datasource.syncType, false);
callback(response);
}
});
return;
}
// Hack: we don't seem to be able to handle
// authentication during an upload request.
// So to avoid trouble, we do a throw-away
// status request first. If auth is required,
// it will be handled during the status request,
// after which we will get on to our real work.
var funcForceAuth = function(status) {
if (self._responseOK(status, callback)) {
//Xmarks.SetProgressMessage("progress.writing");
ns.FetchFromNative(funcFetched);
}
};
var funcFetched = function(e) {
if (e) {
callback(e);
return;
}
ns.ProvideCommandset(funcContinue);
};
var funcContinue = function(status, cs) {
if (self._responseOK(status, callback)) {
self.request = new Request("POST",
{
path: "/sync/" + self._datasource.syncType + "/upload",
host: Xmarks.gSettings.getServerHost(self._datasource.syncType)
},
self._args({ "commands" : cs },
Xmarks.gSettings.mustUpload(self._datasource.syncType)
)
);
self.request.Start(funcDone);
}
};
var funcDone = function(response) {
if (self._responseOK(response, callback)) {
if (!response.toprev) {
Xmarks.LogWrite("Error: Invalid response to upload");
callback(500);
} else {
Xmarks.gSettings.setMustUpload(self._datasource.syncType, false);
self._completeTransaction(ns, { toprev: response.toprev },
callback);
}
}
};
if (Xmarks.gSettings.viewId && !Xmarks.gSettings.mustUpload(self._datasource.syncType)) {
this._uploadWithProfile(callback);
return;
}
self.status(funcForceAuth);
}
Syncd2SyncEngine.prototype.purgepasswords = function(callback) {
var self = this;
var ns = new Nodeset(this._datasource);
// Hack: we don't seem to be able to handle
// authentication during an upload request.
// So to avoid trouble, we do a throw-away
// status request first. If auth is required,
// it will be handled during the status request,
// after which we will get on to our real work.
var funcForceAuth = function(status) {
if (self._responseOK(status, callback)) {
self.request = new Request("POST",
{
path: "/sync/" + self._datasource.syncType + "/purge",
host: Xmarks.gSettings.getServerHost(self._datasource.syncType)
} , self._args({ mode: "data"}));
self.request.Start(function(statuspurge){
if (self._responseOK(statuspurge, callback)) {
Xmarks.gSettings.SetSyncRevision(
self._datasource.syncType, 0);
callback(0);
}
});
}
};
self.status(funcForceAuth);
}
Syncd2SyncEngine.prototype._uploadWithProfile = function(callback) {
var self = this;
var revision;
var ns;
var lns;
var funcDone = function(response) {
if (self._responseOK(response, callback)) {
if (!response.toprev || !response.commands) {
Xmarks.LogWrite("Error: Invalid response to download");
callback(500);
return;
}
revision = response.toprev;
ns = new Nodeset(self._datasource);
var cs = new Commandset(response.commands);
try {
cs.execute(ns);
} catch (e) {
Xmarks.LogWrite("execute failed: " + e.toSource());
if(typeof e == "number"){
callback(e);
} else {
callback(4);
}
return;
}
// Retrieve local set.
lns = new Nodeset(self._datasource);
lns.FetchFromNative(funcGotLocal);
}
};
var funcGotLocal = function(response) {
// Calculate difference: server -> local.
if (self._responseOK(response, callback)) {
ns.Compare(lns, funcCompared);
}
};
var funcCompared = function(status, cs) {
if (self._responseOK(status, callback)) {
if (cs.length) {
//Xmarks.SetProgressMessage("progress.uploading");
self.request = new Request("POST",
{
path: "/sync/" + self._datasource.syncType + "/putchanges",
host: Xmarks.gSettings.getServerHost(self._datasource.syncType)
},
self._args({ "baserev": revision, "commands" : cs }));
self.request.Start(funcFinished);
} else {
funcFinished({ status: 0, toprev: revision});
}
}
};
var funcFinished = function(response) {
if (self._responseOK(response, callback)) {
if (!response.toprev) {
Xmarks.LogWrite("Error: Invalid response to putchanges");
callback(500);
} else {
self._completeTransaction(ns, { toprev: response.toprev },
callback);
}
}
};
// Download current server state.
Xmarks.SetProgressMessage("progress.downloading");
self.request = new Request("POST",
{
path: "/sync/" + self._datasource.syncType + "/download",
host: Xmarks.gSettings.getServerHost(self._datasource.syncType)
}, self._args({}));
self.request.Start(funcDone);
}
Syncd2SyncEngine.prototype.download = function(callback, rev) {
var self = this;
var ns;
var revision;
// resetPIN trumps download (only occurs in the case of
// resetPIN in setup dialog)
if(Xmarks.gSettings.mustUpload(self._datasource.syncType)){
Xmarks.LogWrite("Forced Upload: Pin Reset");
self.upload(function(response){
if(self._responseOK(response, callback)){
Xmarks.gSettings.setMustUpload(self._datasource.syncType, false);
callback(response);
}
});
return;
}
else if(Xmarks.gSettings.mustMerge(self._datasource.syncType)){
Xmarks.LogWrite("Forced Merge for Passwords");
self.merge(true, function(response){
if(self._responseOK(response, callback)){
Xmarks.gSettings.setMustMerge(self._datasource.syncType, false);
callback(response);
}
});
return;
}
var funcDone = function(response) {
if (self._responseOK(response, callback)) {
if (!response.toprev || !response.commands) {
Xmarks.LogWrite("Error: Invalid response to download");
callback(500);
return;
}
revision = response.toprev;
ns = new Nodeset(self._datasource);
var cs = new Commandset(response.commands);
try {
cs.execute(ns);
} catch (e) {
Xmarks.LogWrite("execute failed: " + e.toSource());
if(typeof e == "number"){
callback(e);
} else {
callback(4);
}
return;
}
//Xmarks.SetProgressMessage("progress.loading");
ns.FlushToNative(funcFinished);
}
};
var funcFinished = function(response) {
if (self._responseOK(response, callback)) {
if(!rev){
self._completeTransaction(ns, { toprev: revision }, callback);
} else {
callback(response);
}
}
};
//Xmarks.SetProgressMessage("progress.downloading");
var params = {};
if(rev){
params.rev = rev;
}
self.request = new Request("POST",
{
path: "/sync/" + self._datasource.syncType + "/download",
host: Xmarks.gSettings.getServerHost(self._datasource.syncType)
}, self._args(params));
self.request.Start(funcDone);
}
Syncd2SyncEngine.prototype.verifypinbrokenroot = function(pin, callback) {
var self = this;
var ns;
var revision;
var funcDone = function(response) {
if (self._responseOK(response, callback)) {
for(var nid in response.nodes){
if(!response.nodes.hasOwnProperty(nid)){
continue;
} else if(response.nodes[nid].data){
var result = self._datasource.verifyPin(pin, response.nodes[nid]);
callback(result ? 0 : 100);
return;
}
}
// must have been no nodes with data, so assume it's valide
callback(0);
}
};
//Xmarks.SetProgressMessage("progress.verifying");
Xmarks.LogWrite("Verifying PIN Broken Root");
self.request = new Request("POST",
{
path: "/sync/" + self._datasource.syncType + "/state",
host: Xmarks.gSettings.getServerHost(self._datasource.syncType)
}, {"nodes":"ROOT", "depth":"children"});
self.request.Start(funcDone);
}
Syncd2SyncEngine.prototype.restore = function(rev,callback) {
var self = this;
this.download(
function(response){
if (self._responseOK(response, callback)) {
// self.upload(callback);
self.sync('dirty',
function(response){
if (self._responseOK(response, callback)) {
Xmarks.gSettings.SyncComplete(
self._datasource.syncType
);
}
callback(response);
}
);
}
},
rev
);
}
Syncd2SyncEngine.prototype.getrevision = function(rev,callback) {
var self = this;
var funcFetched = function(response) {
if (self._responseOK(response, callback)) {
callback(0, response);
}
}
//Xmarks.SetProgressMessage("progress.gettingprofilenames");
var request = new Request("POST",
{ path: "/sync/bookmarks/state",
host: Xmarks.gSettings.getServerHost(self._datasource.syncType) },
this._args({rev: rev}) );
request.Start(funcFetched);
}
Syncd2SyncEngine.prototype.getrevisions = function(callback) {
var self = this;
var funcFetched = function(response) {
if (self._responseOK(response, callback)) {
callback(0, response);
}
}
//Xmarks.SetProgressMessage("progress.gettingprofilenames");
var request = new Request("POST",
{ path: "/sync/bookmarks/getrevisions",
host: Xmarks.gSettings.getServerHost(self._datasource.syncType) },
this._args({limit: Xmarks.gSettings.getRevisionLimit}) );
request.Start(funcFetched);
}
Syncd2SyncEngine.prototype.verifypin = function(pin, callback) {
var self = this;
var ns;
var revision;
var funcDone = function(response) {
if (self._responseOK(response, callback)) {
Xmarks.LogWrite("Verifying PIN (Received Test Node)");
if(!response.nodes.ROOT.data){
Xmarks.LogWrite("WARNING: Bad Root Problem");
self.verifypinbrokenroot(pin, callback);
} else {
var result = self._datasource.verifyPin(pin, response.nodes.ROOT);
callback(result ? 0 : 100);
}
}
};
//Xmarks.SetProgressMessage("progress.verifying");
Xmarks.LogWrite("Verifying PIN (Starting)");
self.request = new Request("POST",
{
path: "/sync/" + self._datasource.syncType + "/state",
host: Xmarks.gSettings.getServerHost(self._datasource.syncType)
}, {"nodes":"ROOT", "depth":"self"});
self.request.Start(funcDone);
}
// Fetch changes from the server. Passes to callback an object containing:
// * continous: true if the changes on the server are continuous.
// * mscs: the minimal server commandset.
// * other server context info (in this case, toprev) to passed into
// _writeServerChanges().
Syncd2SyncEngine.prototype._getServerChanges = function(callback) {
// Get changes.
var self = this;
var toprev = 0;
var sns = new Nodeset(self._datasource, self._baseline);
var funcProcessResponse = function(response) {
if (self._responseOK(response, callback)) {
if (response.continuous == false) {
callback(0, { continuous: false });
} else {
if (!response.toprev || !response.commands) {
Xmarks.LogWrite("Error: Invalid response to getchanges");
callback(500);
return;
}
toprev = response.toprev;
// Calculate mcs.
if (response.commands.length) {
scs = new Commandset(response.commands);
try {
scs.execute(sns);
} catch (e) {
Xmarks.LogWrite("execute failed: " + e.toSource());
if(typeof e == "number"){
callback(e);
} else {
callback(4);
}
return;
}
var base = new Nodeset(self._datasource,self._baseline);
base.Compare(sns, funcCalculatedMcs);
} else {
// Not modified
callback(0, { mscs: new Commandset(), toprev: toprev,
sns: sns, continuous: true});
}
}
}
};
var funcCalculatedMcs = function(status, cs) {
if (self._responseOK(status, callback)) {
Xmarks.LogWrite(">>> Finished mcs: " + cs.toSource());
callback(0, { mscs: cs, toprev: toprev, continuous: true,
sns: sns });
}
};
self.request = new Request("POST",
{
path: "/sync/" + self._datasource.syncType + "/getchanges",
host: Xmarks.gSettings.getServerHost(self._datasource.syncType)
},
self._args({ "baserev": self._baseline.currentRevision}) );
self.request.Start(funcProcessResponse);
}
OwnSyncEngine.prototype.testDupURL = function(){
// force check to see if user has two different urls for password
// and bookmark
if(Xmarks.gSettings.isSyncEnabled("bookmarks") &&
Xmarks.gSettings.isSyncEnabled("passwords")){
if(Xmarks.gSettings.getUrlWithUsernameAndPassword("passwords") ==
Xmarks.gSettings.getUrlWithUsernameAndPassword("bookmarks")){
return true;
}
}
return false;
}
OwnSyncEngine.prototype._getServerChanges = function(callback) {
// Download the file, but only if the etag doesn't match
var self = this;
var headers = {};
var token;
var etag;
var sns;
var funcProcessFile = function(response) {
if (response.status == 304) { // Not modified.
callback(0, { continuous: true, mscs: new Commandset(),
token: Xmarks.gSettings.getToken(self._datasource.syncType), etag: response.etag });
return;
}
if (self._responseOK(response, callback)) {
if (response.token != Xmarks.gSettings.getToken(self._datasource.syncType)) {
callback(0, { continuous: false, etag: response.etag } );
return;
}
token = response.token;
etag = response.etag;
var cs = new Commandset(response.commands);
sns = new Nodeset(self._datasource);
try {
sns.Execute(cs);
} catch (e) {
Xmarks.LogWrite("execute failed: " + e.toSource());
if(typeof e == "number"){
callback(e);
} else {
callback(4);
}
return;
}
var base = new Nodeset(self._datasource,self._baseline);
base.Compare(sns, funcCompared);
}
};
var funcCompared = function(status, cs) {
if (self._responseOK(status)) {
callback(0, { continuous: true, mscs: cs, token: token,
sns: sns, etag: etag });
}
};
var serveretag = Xmarks.gSettings.getEtag(this._datasource.syncType);
if (serveretag.length) {
headers["If-None-Match"] = serveretag;
}
self.request = new Request("GET",
Xmarks.gSettings.getUrlWithUsernameAndPassword(
self._datasource.syncType
),
null,
{
isAuthRequest: false,
headers: headers
}
);
self.request.Start(funcProcessFile);
}
Syncd2SyncEngine.prototype._writeServerChanges = function(lcs, lns, sco, conflicts,
callback) {
var self = this;
var funcWrote = function(response) {
if (self._responseOK(response, callback)) {
sco.toprev = response.toprev;
if (!sco.toprev) {
Xmarks.LogWrite("Error: Invalid response to putchanges");
callback(500);
} else {
callback(0);
}
}
};
self.request = new Request("POST",
{
path: "/sync/" + self._datasource.syncType + "/putchanges",
host: Xmarks.gSettings.getServerHost(self._datasource.syncType)
},
self._args({ baserev : sco.toprev, commands : lcs, log : conflicts } ));
self.request.Start(funcWrote);
}
OwnSyncEngine.prototype._writeServerChanges = function(lcs, lns, sco, conflicts,
callback) {
var self = this;
var funcGotCommandset = function(status, cs) {
var header = {};
if (!Xmarks.gSettings.disableIfMatchOnPut && sco.etag && sco.etag.length) {
header = { "Etag" : sco.etag };
}
if (self._responseOK(status, callback)) {
self.request = new Request("PUT",
Xmarks.gSettings.getUrlWithUsernameAndPassword(self._datasource.syncType),
{ token: sco.token, commands: cs },
{
isAuthRequest: false,
headers: header,
ignoreBody: true
}
);
self.request.Start(funcWritten);
}
};
var funcWritten = function(response) {
if (self._responseOK(response, callback)) {
sco.etag = response.etag;
callback(0);
}
};
lns.ProvideCommandset(funcGotCommandset);
}
Syncd2SyncEngine.prototype.status = function(callback) {
var self = this;
var funcCheckStatus = function(response) {
if (self._responseOK(response, callback)) {
if (!response.toprev) {
Xmarks.LogWrite("Error: Invalid response to status");
callback(500);
return;
}
callback(0, response);
}
};
Xmarks.LogWrite("Entered Status...");
//Xmarks.SetProgressMessage("progress.verifying");
self.request = new Request("POST",
{ path: "/sync/" + self._datasource.syncType + "/status",
host: Xmarks.gSettings.getServerHost(self._datasource.syncType)
}, {});
self.request.Start(funcCheckStatus);
}
Syncd2SyncEngine.prototype.extstatus = function(callback) {
var self = this;
this.status(function(normal_status,response){
// if we get an error, do callback right away
if(normal_status){
callback(normal_status, response);
// if we are reset, no need to check if we are purged
} else if(response.isreset){
response.ispurged = false;
callback(normal_status, response);
// call for the root to see if we get a 444
} else {
self.request = new Request("POST",
{
path: "/sync/" + self._datasource.syncType + "/state",
host: Xmarks.gSettings.getServerHost(self._datasource.syncType)
}, {"nodes":"ROOT", "depth":"self"});
self.request.Start(function(stateresponse){
if (self._responseOK(stateresponse, function(errnum){
if(errnum == 444){
response.ispurged = true;
callback(0, response);
} else {
callback(errnum, response);
}
})) {
response.ispurged = false;
callback(0, response);
}
});
}
});
}
OwnSyncEngine.prototype.status = function(callback) {
var self = this;
var funcHandleResponse = function(response) {
if (response.status == 404) {
callback(0, { status: 0, isreset: true, ispurged: false });
} else if (self._responseOK(response, callback)) {
callback(0, { status: 0, isreset: false, ispurged: false });
}
};
Xmarks.LogWrite("Entered status...");
if(this.testDupURL()){
callback(1011);
return;
}
//Xmarks.SetProgressMessage("progress.verifying");
self.request = new Request("GET",
Xmarks.gSettings.getUrlWithUsernameAndPassword(self._datasource.syncType));
self.request.Start(funcHandleResponse);
}
OwnSyncEngine.prototype.extstatus = OwnSyncEngine.prototype.status;
Syncd2SyncEngine.prototype._getServerState = function(callback) {
var self = this;
self.request = new Request("POST",
{
path: "/sync/" + self._datasource.syncType + "/download",
host: Xmarks.gSettings.getServerHost(self._datasource.syncType)
}, self._args({}) );
self.request.Start(callback);
}
OwnSyncEngine.prototype._getServerState = function(callback) {
var self = this;
self.request = new Request("GET",
Xmarks.gSettings.getUrlWithUsernameAndPassword(self._datasource.syncType));
self.request.Start(callback);
}
SyncEngine.prototype.merge = function(local, callback) {
// Perform a merge (an "additive sync").
// local is a boolean which determines what merge uses
// as the starting set: true for local, false for server.
Xmarks.LogWrite("Entered Merge...");
var self = this;
var serverNS = new Nodeset(self._datasource);
var localNS = new Nodeset(self._datasource);
var mergedNS = null;
var revision;
var sco;
// resetPIN trumps merge
if(Xmarks.gSettings.mustUpload(self._datasource.syncType)){
Xmarks.LogWrite("Forced Upload: Pin Reset");
self.upload(function(response){
if(self._responseOK(response, callback)){
Xmarks.gSettings.setMustUpload(self._datasource.syncType, false);
callback(response);
}
});
return;
}
var funcDone = function(response) {
if (self._responseOK(response, callback)) {
sco = response;
var cs = new Commandset(response.commands);
try {
cs.execute(serverNS);
} catch (e) {
if(typeof e == "number"){
callback(e);
} else {
Xmarks.LogWrite("execute failed: " + e.toSource());
callback(4);
}
return;
}
localNS.FetchFromNative(funcFetched);
}
};
var funcFetched = function( e ) {
if (e) {
callback(e);
return;
}
//Xmarks.SetProgressMessage("progress.merging");
if (local) {
localNS.Merge(serverNS);
mergedNS = localNS;
} else {
mergedNS = new Nodeset(self._datasource, serverNS);
mergedNS.Merge(localNS);
}
// calculate server's MCS
Xmarks.LogWrite("Finished merge; calculating mcs");
serverNS.Compare(mergedNS, funcWriteChanges);
};
var funcWriteChanges = function(status, cs) {
if (self._responseOK(status, callback)) {
// Write changes to the server.
//Xmarks.SetProgressMessage("progress.writing");
if (cs.set.length > 0) {
self._writeServerChanges(cs, mergedNS, sco, null,
funcWroteServerChanges);
} else {
funcWroteServerChanges(0);
}
}
};
var funcWroteServerChanges = function(response) {
// Write 'em back to native store, too
if (self._responseOK(response, callback)) {
//Xmarks.SetProgressMessage("progress.loading");
mergedNS.FlushToNative(funcFlushed);
}
};
var funcFlushed = function(response) {
if (self._responseOK(response, callback)) {
Xmarks.gSettings.setMustMerge(self._datasource.syncType, false);
self._completeTransaction(mergedNS, sco, callback);
}
};
//Xmarks.SetProgressMessage("progress.downloading");
self._getServerState(funcDone);
}
Syncd2SyncEngine.prototype._fetchBaseline = function(callback) {
// Fetch the baseline if necessary
var self = this;
var funcLoadedFromServer = function(response) {
if (self._responseOK(response, callback)) {
var cs = new Commandset(response.commands);
try {
cs.execute(self._baseline);
} catch (e) {
Xmarks.LogWrite("execute failed: " + e.toSource());
if(typeof e == "number"){
callback(e);
} else {
callback(4);
}
return;
}
self._baseline.currentRevision = response.rev;
self._baseline.SaveToFile(funcLoaded);
return;
} else {
Xmarks.LogWrite("Failed to load baseline from server.");
}
};
var funcLoaded = function() {
// Loaded from file or server
// var nat = new NativeDatasource();
self._baseline.BaselineLoaded(self._baseline, funcGotIt);
return;
};
var funcGotIt = function(status) {
if (self._responseOK(status, callback)) {
self._baseline.hash = Xmarks.gSettings.hash;
callback(0);
}
};
if (self._baseline && self._baseline.hash == Xmarks.gSettings.hash) {
Xmarks.LogWrite("Using Baseline Cache.");
funcGotIt(0);
return;
}
self._baseline = new Nodeset(self._datasource);
try {
self._baseline.LoadFromFile();
} catch (e) {
// We failed to load our baseline locally -- try getting
// it from the server.
Xmarks.LogWrite("Failed to load baseline from file: " + e.name);
var request = new Request("POST",
{
path: "/sync/" + self._datasource.syncType + "/download",
host: Xmarks.gSettings.getServerHost(self._datasource.syncType)
},
self._args({ rev: Xmarks.gSettings.GetSyncRevision(self._datasource.syncType), depth: "all",
log: { "error": e.name }}));
request.Start(funcLoadedFromServer);
return;
}
funcLoaded();
}
OwnSyncEngine.prototype._fetchBaseline = function(callback) {
// Fetch the baseline if necessary
var self = this;
var funcLoaded = function() {
// Loaded from file
// var nat = new NativeDatasource();
self._baseline.BaselineLoaded(self._baseline, funcGotIt);
};
var funcGotIt = function(status) {
if (self._responseOK(status, callback)) {
self._baseline.hash = Xmarks.gSettings.hash;
callback(0);
}
};
if (self._baseline && self._baseline.hash == Xmarks.gSettings.hash) {
funcGotIt(0);
return;
}
self._baseline = new Nodeset(self._datasource);
try {
self._baseline.LoadFromFile();
} catch (e) {
// We failed to load our baseline locally -- we're hosed.
Xmarks.LogWrite("Failed to load baseline.");
if(typeof e == "number"){
callback(e);
} else {
callback(4);
}
return;
}
funcLoaded();
}
Syncd2SyncEngine.prototype._completeTransaction = function(ns, sco, callback) {
var self = this;
var funcFinishUp = function(status) {
if (self._responseOK(status, callback)) {
self._baseline = ns;
self._baseline.hash = Xmarks.gSettings.hash;
self._baseline.currentRevision = sco.toprev;
self._baseline.SaveToFile(funcFinishedWrite);
}
};
var funcFinishedWrite = function(status) {
if (self._responseOK(status, callback)) {
// Xmarks.gSettings.currentRevision = sco.toprev;
Xmarks.gSettings.SetSyncRevision(self._datasource.syncType, sco.toprev);
callback(0);
}
};
ns.Declone(funcFinishUp);
}
Syncd2SyncEngine.prototype.getProfileNames = function(callback) {
var self = this;
var funcFetched = function(response) {
if (self._responseOK(response, callback)) {
callback(0, response);
}
}
//Xmarks.SetProgressMessage("progress.gettingprofilenames");
var request = new Request("POST",
{ path: "/user/profiles/getnames",
host: Xmarks.gSettings.acctMgrHost },
this._args({}) );
request.Start(funcFetched);
}
OwnSyncEngine.prototype.upload = function(callback) {
var self = this;
var ns = new Nodeset(self._datasource);
var token = Date.now().toString(16);
// if we are doing a force upload, then that trumps forcedMerge
if(Xmarks.gSettings.mustUpload(self._datasource.syncType)){
Xmarks.gSettings.setMustMerge(self._datasource.syncType, false);
}
// even though the user says upload, we don't want
// to do that for passwords during initial sync
if(Xmarks.gSettings.mustMerge(self._datasource.syncType)){
Xmarks.LogWrite("Forced Merge for Passwords");
self.merge(true, function(response){
if(self._responseOK(response, callback)){
Xmarks.gSettings.setMustMerge(self._datasource.syncType, false);
callback(response);
}
});
return;
}
var funcFetched = function(status) {
if (self._responseOK(status, callback)) {
ns.ProvideCommandset(funcContinue);
}
};
var funcContinue = function(status, cs) {
if (self._responseOK(status, callback)) {
self.request = new Request("PUT",
Xmarks.gSettings.getUrlWithUsernameAndPassword(self._datasource.syncType),
{ commands: cs, token: token },
{ ignoreBody: true }
);
self.request.Start(funcDone);
}
};
var funcDone = function(response) {
if (self._responseOK(response, callback)) {
Xmarks.gSettings.setMustUpload(self._datasource.syncType, false);
self._completeTransaction(ns,
{ etag: response.etag, token: token }, callback);
}
};
//Xmarks.SetProgressMessage("progress.writing");
ns.FetchFromNative(funcFetched);
}
OwnSyncEngine.prototype.verifypin = function(pin, callback) {
var self = this;
var ns;
var funcDone = function(response) {
var funcFinished = function(resp) {
if (self._responseOK(resp, callback)) {
self._completeTransaction(ns,
{ etag: response.etag, token: response.token }, callback);
}
};
if (self._responseOK(response, callback)) {
ns = new Nodeset(self._datasource);
var cs = new Commandset(response.commands);
try {
cs.execute(ns);
} catch (e) {
Xmarks.LogWrite("execute failed: " + e.toSource());
if(typeof e == "number"){
callback(e);
} else {
callback(4);
}
return;
}
var result = self._datasource.verifyPin(pin, ns.Node(NODE_ROOT));
callback(result ? 0 : 100);
}
};
//Xmarks.SetProgressMessage("progress.verifying");
Xmarks.LogWrite("Verifying PIN (Own Server)");
self.request = new Request("GET", Xmarks.gSettings.getUrlWithUsernameAndPassword(self._datasource.syncType));
self.request.Start(funcDone);
}
OwnSyncEngine.prototype.download = function(callback) {
var self = this;
var ns;
// resetPIN trumps download (only occurs in the case of
// resetPIN in setup dialog)
if(Xmarks.gSettings.mustUpload(self._datasource.syncType)){
Xmarks.LogWrite("Forced Upload: Pin Reset");
self.upload(function(response){
if(self._responseOK(response, callback)){
Xmarks.gSettings.setMustUpload(self._datasource.syncType, false);
callback(response);
}
});
return;
}
else if(Xmarks.gSettings.mustMerge(self._datasource.syncType)){
Xmarks.LogWrite("Forced Merge for Passwords");
self.merge(true, function(response){
if(self._responseOK(response, callback)){
Xmarks.gSettings.setMustMerge(self._datasource.syncType, false);
callback(response);
}
});
return;
}
var funcDone = function(response) {
var funcFinished = function(resp) {
if (self._responseOK(resp, callback)) {
self._completeTransaction(ns,
{ etag: response.etag, token: response.token }, callback);
}
};
if (self._responseOK(response, callback)) {
ns = new Nodeset(self._datasource);
var cs = new Commandset(response.commands);
try {
cs.execute(ns);
} catch (e) {
Xmarks.LogWrite("execute failed: " + e.toSource());
if(typeof e == "number"){
callback(e);
} else {
callback(4);
}
return;
}
//Xmarks.SetProgressMessage("progress.loading");
ns.FlushToNative(funcFinished);
}
};
//Xmarks.SetProgressMessage("progress.downloading");
self.request = new Request("GET", Xmarks.gSettings.getUrlWithUsernameAndPassword(self._datasource.syncType));
self.request.Start(funcDone);
}
OwnSyncEngine.prototype._completeTransaction =
function(ns, sco, callback) {
var self = this;
var funcFinishUp = function(status) {
if (self._responseOK(status, callback)) {
self._baseline = ns;
self._baseline.hash = Xmarks.gSettings.hash;
self._baseline.SaveToFile(funcFinishedWrite);
}
};
var funcFinishedWrite = function(status) {
if (self._responseOK(status, callback)) {
Xmarks.gSettings.setToken(self._datasource.syncType, sco.token);
if (sco.etag) Xmarks.gSettings.setEtag(self._datasource.syncType, sco.etag);
Xmarks.gSettings.SyncComplete(self._datasource.syncType);
callback(0);
}
};
ns.Declone(funcFinishUp);
}